/*
 * Ped-ome - main class
 *
 * Joe Handford aka Knecht 2011
 *
 */ 

// GUI Include Files
import java.awt.BorderLayout;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.GridLayout;
import java.awt.FlowLayout;
import java.awt.ComponentOrientation;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Color;
import java.awt.event.*;
import javax.swing.*;

// Misc Include Files
import java.util.*;
import java.io.*;

// Bonjour Include Files
import com.apple.dnssd.*;

// aetherworks imports
import uk.co.aetherworks.active_devices.*;
import uk.co.aetherworks.debug.*;
import uk.co.aetherworks.gui.*;
import uk.co.aetherworks.osc_messaging.*;
import uk.co.aetherworks.serial_osc.*;


public class Pedome extends JFrame
        implements MouseListener, KeyListener,
        ActionListener, OSCCmdHandler, ActiveDevicePublisher, WindowListener, RegisterListener
{
	public enum PedalState {
		OFF, OFF_DOWN, OFF_LEFT, OFF_RIGHT, OFF_UP, ON
	}

	// User interface controls
    JTextArea displayArea;
    Container monomeDisplay;
    static final String newline = System.getProperty("line.separator");
    
    JButton leds[][] = new JButton[8][8];
	JButton setUpButton;
	JButton multiKeyButton;
	Color multiKeyButtonOffColor;
	
	DefaultListModel listModel, origListModel;
	JList buttonSeqList;
	int buttonListIndex = 0;
    
	// Dictionary of buttons
	Map<String, MonomeButton> monome_buttons;
	
	JComboBox deviceSelection;
	
	int ped_to_mon_port = -1;		// set from selected detected monome device service
	int mon_to_ped_port = 9000;		// or get next free one
	int ped_to_app_port = -1;		// need to pick up port sent to me in a /sys/port message
	int app_to_ped_port = 9002;		// or get next free one
	
	String appPrefix = "/pedome";

	// SerialOSC Interface objects
	ActiveDevices activeDevices;
	SerialOSCListener serialOSCListener;
	DNSSDRegistration pedomeService;
	
	// Monome - Pedome - App comms objects
	PedToMon pedToMon;
	MonToPed monToPed;
	PedToApp pedToApp;
	AppToPed appToPed;
	
	// Connection status
	boolean sys_port_response_sent_to_app = false;
	boolean sys_prefix_response_sent_to_app = false;
	boolean sys_info_response_sent_to_app = false;
	
	boolean ped_and_mon_connected = false;
	boolean ped_and_app_connected = false;

	// Pedome status
	boolean setupMode;
	boolean multiKeyMode;
	
	// Button sequence transpose controls
	JComboBox transposeDirectionSelection;
	JComboBox transposeStepSelection;
	JComboBox transposeRangeSelection;
	String transposeDir;
	int transposeStep, transposeRange, numTransposes;
	
	JLabel pedalPicture;
	
	// Debug output handler
	static Debuggable debugger = new Debuggable(); // Pedome can't extend multiple classes so have a Debuggable attribute

	public static void main(String[] args) {
		debugger.DEBUG("App starting....");
		
        try {
			// Use Metal look and feel
            UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
        } catch (UnsupportedLookAndFeelException ex) {
            ex.printStackTrace();
        } catch (IllegalAccessException ex) {
            ex.printStackTrace();
        } catch (InstantiationException ex) {
            ex.printStackTrace();
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }
        // Turn off metal's use of bold fonts
        UIManager.put("swing.boldMetal", Boolean.FALSE);
        
        // Schedule a job for event dispatch thread:
        // creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
    
	public void operationFailed(DNSSDService arg0, int arg1) {
		System.out.println("Operation failed: " + arg0 + " [" + arg1 + "]");
	}

	public void serviceRegistered(DNSSDRegistration registration, int flags, java.lang.String serviceName, java.lang.String regType, java.lang.String domain) {
		System.out.println(serviceName + " service registered.");
	}

    protected void updatePedalPicture(PedalState pedalState) {
        //Get the icon corresponding to the image.
		ImageIcon icon;
		
		switch (pedalState) {
			case ON:        icon = createImageIcon("images/pedome_on.png"); break;
			case OFF_DOWN:  icon = createImageIcon("images/pedome_off_down.png"); break;
			case OFF_RIGHT: icon = createImageIcon("images/pedome_off_right.png"); break;
			case OFF_LEFT:  icon = createImageIcon("images/pedome_off_left.png"); break;
			case OFF_UP:    icon = createImageIcon("images/pedome_off_up.png"); break;
			default:        icon = createImageIcon("images/pedome_off.png"); break;
		}
			
        pedalPicture.setIcon(icon);
        if (icon == null) {
            pedalPicture.setText("Missing Image");
        } else {
            pedalPicture.setText(null);
        }
    }
 
    protected static ImageIcon createImageIcon(String path) {
        java.net.URL imgURL = Pedome.class.getResource(path);
        if (imgURL != null) {
            return new ImageIcon(imgURL);
        } else {
            System.err.println("Couldn't find file: " + path);
            return null;
        }
    }

    private static void createAndShowGUI() {
		//Create and set up the window.
        Pedome frame = new Pedome("Pedome");
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

        //Set up the content pane.
        frame.addComponentsToPane();
        
        //Display the window.
        frame.pack();
        frame.setVisible(true);
		
		try {
			frame.initialise();
		} catch (InterruptedException err) {
			err.printStackTrace();
		}
    }
    
    private void addComponentsToPane() {
        
        // Initialise state of app
		setupMode = false;
		multiKeyMode = false;
		
		transposeDir = "Off";
		transposeStep = 0;
		transposeRange = 0;
		numTransposes = 0;
		debugger.DEBUG("transposeRange initialised to: " + transposeRange);
		
        JButton clearButton = new JButton("Clear");
		clearButton.setActionCommand("clear");
        clearButton.addActionListener(this);
		
        JButton connectButton = new JButton("Connect");
		connectButton.setActionCommand("connect");
        connectButton.addActionListener(this);

		setUpButton = new JButton("Select Set Up Mode");
		setUpButton.setActionCommand("set up");
        setUpButton.addActionListener(this);
		
		multiKeyButton= new JButton("Multi-key");
		multiKeyButton.setActionCommand("multi-key");
        multiKeyButton.addActionListener(this);
		multiKeyButtonOffColor = multiKeyButton.getBackground();
		
		JLabel transposeControlsLabel = new JLabel("Transpose:");

		String[] transposeDirectionStrings = { "Up", "Down", "Left", "Right", "Off" };
		transposeDirectionSelection = new JComboBox(transposeDirectionStrings);
		transposeDirectionSelection.setSelectedIndex(4);
		transposeDirectionSelection.setActionCommand("transpose dir");
        transposeDirectionSelection.addActionListener(this);

		String[] transposeDirectionSteps = { "0", "1", "2", "3", "4", "5", "6", "7" };
		transposeStepSelection = new JComboBox(transposeDirectionSteps);
		transposeStepSelection.setSelectedIndex(0);
		transposeStepSelection.setActionCommand("transpose step");
        transposeStepSelection.addActionListener(this);

		String[] transposeDirectionRanges = { "0", "1", "2", "3", "4", "5", "6", "7" };
		transposeRangeSelection = new JComboBox(transposeDirectionRanges);
		transposeRangeSelection.setSelectedIndex(0);
		transposeRangeSelection.setActionCommand("transpose range");
        transposeRangeSelection.addActionListener(this);

		listModel = new DefaultListModel();
		origListModel = new DefaultListModel();
		buttonSeqList = new JList(listModel);
		
        JScrollPane listScrollPane = new JScrollPane(buttonSeqList);
        listScrollPane.setPreferredSize(new Dimension(50, 125));
				
		addWindowListener(this);

		// Setup device selection field
		debugger.DEBUG("About to create device combo box...");
		activeDevices.printDevices();
		deviceSelection = new JComboBox(activeDevices.getDevices());

		// Set up monome buttons
        for (int y = 0; y < 8; y++)
        {
        	for (int x = 0; x < 8; x++)
        	{
        		leds[x][y] = new JButton("");
        		leds[x][y].setBackground(Color.lightGray);
				leds[x][y].setActionCommand(x + " " + y + " button");
				leds[x][y].addActionListener(this);
        	}
        }
        
        pedalPicture = new JLabel();
		updatePedalPicture(PedalState.OFF);
		
        displayArea = new JTextArea();
        displayArea.setEditable(false);
        displayArea.addKeyListener(this);
        displayArea.addMouseListener(this);
        JScrollPane scrollPane = new JScrollPane(displayArea);
        scrollPane.setPreferredSize(new Dimension(375, 125));
        
        monomeDisplay = new Container();
        monomeDisplay.setPreferredSize(new Dimension(375, 375));
        
        monomeDisplay.setLayout(new GridLayout (8, 8, 5, 5));
        for (int y = 0; y < 8; y++)
        {
        	for (int x = 0; x < 8; x++)
        	{
        		monomeDisplay.add(leds[x][y]);
        	}
        }

		JPanel controlsPanel = new JPanel();
        controlsPanel.setLayout(new FlowLayout());
		controlsPanel.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
		controlsPanel.add(deviceSelection);
		controlsPanel.add(connectButton);
		controlsPanel.add(setUpButton);
		controlsPanel.add(multiKeyButton);
		controlsPanel.add(transposeControlsLabel);
		controlsPanel.add(transposeDirectionSelection);
		controlsPanel.add(transposeStepSelection);
		controlsPanel.add(transposeRangeSelection);
//		controlsPanel.add(clearButton);
		
		JPanel statusPanel = new JPanel();
        statusPanel.setLayout(new FlowLayout());
		statusPanel.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
		statusPanel.add(clearButton);
		statusPanel.add(listScrollPane);
		statusPanel.add(scrollPane);

		JPanel displayPanel = new JPanel();
        displayPanel.setLayout(new FlowLayout());
		displayPanel.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
		displayPanel.add(pedalPicture);
		displayPanel.add(monomeDisplay);

		getContentPane().setLayout(new BorderLayout());
		getContentPane().add(controlsPanel, BorderLayout.PAGE_START);
		getContentPane().add(displayPanel, BorderLayout.CENTER);
		getContentPane().add(statusPanel, BorderLayout.PAGE_END);
    }
    
    public void windowClosing(WindowEvent e) {
		debugger.DEBUG("Closing window...");

		if (monToPed != null) {
			debugger.DEBUG("Detaching pedToApp from monToPed");
			monToPed.detachForwarder();
		}

		if (appToPed != null) {
			debugger.DEBUG("Detaching pedToMon from appToPed");
			appToPed.detachForwarder();
		}

		if (pedToApp != null) {
			debugger.DEBUG("Closing pedToApp...");
			pedToApp.close();
		}
		
		if (pedToMon != null) {
			debugger.DEBUG("Closing pedToMon...");
			pedToMon.close();
		}
		
		if (pedomeService != null) {
			debugger.DEBUG("Stopping pedome service...");
			pedomeService.stop();
		}
		
		if (appToPed != null) {
			debugger.DEBUG("Closing appToPed...");
			appToPed.close();
		}

		if (monToPed != null) {
			debugger.DEBUG("Closing monToPed...");
			monToPed.close();
		}

		this.setVisible( false );
		this.dispose();
        System.exit(0);
    }

    public void windowClosed(WindowEvent e) {
		debugger.DEBUG("Closing window...");
    }
    
    public void windowOpened(WindowEvent e) {
		debugger.DEBUG("Opening window...");
    }
    
    public void windowIconified(WindowEvent e) {
		debugger.DEBUG("Iconifying window...");
    }
    
    public void windowDeiconified(WindowEvent e) {
		debugger.DEBUG("Deiconifying window...");
    }
    
    public void windowActivated(WindowEvent e) {
		debugger.DEBUG("Activating window...");
    }
    
    public void windowDeactivated(WindowEvent e) {
		debugger.DEBUG("Deactivating window...");
    }

    public void publishDevices(ActiveDevices devices) {
		// Wire-up step a3a: Publish devices. ----------------------- //
		debugger.DEBUG("a3a. Publish devices...");
		debugger.DEBUG("About to update device name combo box....");
		String[] deviceNames = devices.getDevices();
		debugger.DEBUG("deviceNames.length: " + deviceNames.length);
		for (int i = 0; i < deviceNames.length; i++) {
			debugger.DEBUG("in combo box populate loop i = " + i + "; device name = " + deviceNames[i]);		
			deviceSelection.addItem(deviceNames[i]);
		}
	}

	public void initialise()
		throws InterruptedException  
	{
		/* Process to wire up monome - pedome - app as follows:
		
			On Pedome creation:
			a1.  Create MonToPed using fixed mon_to_ped_port.
			a2.  Create AppToPed using fixed app_to_ped_port.
			a3.  Discover devices.
			a3a. Publish devices.
			a4.  Broadcast pedome's fixed app listening port (app_to_ped_port).
			
			Note. Don't want pedome to appear in list of services.
			
			On device selection
			b1.  Set ped_to_mon_port.
			b2.  Create PedToMon.
			b3.  Request PedToMon send ped's mon listening port to mon via /sys/port.
			b3a. Request PedToMon send ped's prefix to mon via /sys/port.
			b4.  Request PedToMon get mon's status via /sys/info and record mon's prefix.

			Upon receipt of /sys/port by AppToPed:
			c1.  Set ped_to_app_port.
			c2.  Create PedToApp.
			c3.  Respond with /sys/port ped_to_app_port.
			
			Upon receipt of /sys/prefix by AppToPed:
			x1.  Set app prefix
			x2.  Respond with /sys/prefix <app prefix>

			Upon receipt of /sys/status by AppToPed:
			d1.  Send status to App
			
			When both PedToMon and PedToApp have been created
			e1. Attach PedToMon (forwarder) to AppToPed. Update Listeners for latter.
			e2. Attach PedToApp (forwarder) to MonToPed. Update Listeners for latter.
		*/

		// Wire-up step a1:  Create MonToPed,
		debugger.DEBUG("a1. Creating MonToPed...");
		monToPed = new MonToPed(mon_to_ped_port, this);
		
		// Wire-up step a2:  Create AppToPed using fixed app_to_ped_port,
		debugger.DEBUG("a2. Create AppToPed...");
		appToPed = new AppToPed(app_to_ped_port, this);

		// Wire-up step a3: Discover active monome devices ----------- //
		debugger.DEBUG("a3. Browsing for active monome devices...");
		//SerialOSCListener serialOSCListener = new SerialOSCListener(activeDevices, this);
		serialOSCListener = new SerialOSCListener(activeDevices, this);
		DNSSDService browseService = null;
		
		// This will browse bonjour for monome devices, might have to change
		// this for arc
		try {
			browseService = DNSSD.browse("_monome-osc._udp", serialOSCListener);
		} catch (DNSSDException e) {
			e.printStackTrace();
		}
		
		// Wait one second - this should be long enough to detect active monome device
		Thread.sleep(1000);
		
		if (browseService != null) {
			browseService.stop();
			System.out.println("Stopping browsing...");
		}	
		// End of device discovery ---------------------------------- //
		
		// Wire-up step a4: Broadcast pedome's app listening port -----//
		debugger.DEBUG("a4. Broadcasting pedome's app listening port...");
		try {
			pedomeService = DNSSD.register("pedome", "_monome-osc._udp", app_to_ped_port, this);
		} catch (DNSSDException err) {
			err.printStackTrace();
		}
		// End of pedome broadcast -----------------------------------//		

	}
	
	public Pedome(String name) {
        super(name);

		activeDevices = new ActiveDevices();

		// Create dictionary to hold monome buttons
    	monome_buttons = new HashMap<String, MonomeButton>();
    	
    	// Populate dictionary
    	monome_buttons.put("1", new MonomeButton(0,4));
    	monome_buttons.put("2", new MonomeButton(1,4));
    	monome_buttons.put("3", new MonomeButton(2,4));
    	monome_buttons.put("4", new MonomeButton(3,4));
    	monome_buttons.put("5", new MonomeButton(4,4));
    	monome_buttons.put("6", new MonomeButton(5,4));
    	monome_buttons.put("7", new MonomeButton(6,4));
    	monome_buttons.put("8", new MonomeButton(7,4));
    	monome_buttons.put("0", new MonomeButton(9,4));    	

    	monome_buttons.put("q", new MonomeButton(0,5));
    	monome_buttons.put("w", new MonomeButton(1,5));
    	monome_buttons.put("e", new MonomeButton(2,5));
    	monome_buttons.put("r", new MonomeButton(3,5));
    	monome_buttons.put("t", new MonomeButton(4,5));
    	monome_buttons.put("y", new MonomeButton(5,5));
    	monome_buttons.put("u", new MonomeButton(6,5));
    	monome_buttons.put("i", new MonomeButton(7,5));

    	monome_buttons.put("a", new MonomeButton(0,6));
    	monome_buttons.put("s", new MonomeButton(1,6));
    	monome_buttons.put("d", new MonomeButton(2,6));
    	monome_buttons.put("f", new MonomeButton(3,6));
    	monome_buttons.put("g", new MonomeButton(4,6));
    	monome_buttons.put("h", new MonomeButton(5,6));
    	monome_buttons.put("j", new MonomeButton(6,6));
    	monome_buttons.put("k", new MonomeButton(7,6));

    	monome_buttons.put("z", new MonomeButton(0,7));
    	monome_buttons.put("x", new MonomeButton(1,7));
    	monome_buttons.put("c", new MonomeButton(2,7));
    	monome_buttons.put("v", new MonomeButton(3,7));
    	monome_buttons.put("b", new MonomeButton(4,7));
    	monome_buttons.put("n", new MonomeButton(5,7));
    	monome_buttons.put("m", new MonomeButton(6,7));
    	monome_buttons.put(",", new MonomeButton(7,7));
    }
    
    
    /** Handle the key typed event from the text field. */
    public void keyTyped(KeyEvent e) {
    }
    
    /** Handle the key pressed event from the text field. */
    public void keyPressed(KeyEvent e) {
    	char key = e.getKeyChar();
    	String key_string = Character.toString(key);
    	
    	if (monome_buttons.containsKey(key_string))
    	{
	    	int x = monome_buttons.get(key_string).getX();
	    	int y = monome_buttons.get(key_string).getY();
	    	boolean state = monome_buttons.get(key_string).getState();

        	if (state == false)
        	{
        		displayInfo(e, "KEY PRESSED: ");
        		monome_buttons.get(key_string).setState(true);
        		if (x!= 9)
        		{
        			if (monome_buttons.get("0").getState() == true)
        			{
        				y = y - 4;
        			}
        			pedToApp.sendMsg(x, y, 1);
//					displayDebugInfo(x, y, 1);
        		}
        	}
    	}
    }
    
    /** Handle the key released event from the text field. */
    public void keyReleased(KeyEvent e) {
    	char key = e.getKeyChar();
    	String key_string = Character.toString(key);
    	
       	if (monome_buttons.containsKey(key_string))
    	{
	    	int x = monome_buttons.get(key_string).getX();
	    	int y = monome_buttons.get(key_string).getY();
	    	boolean state = monome_buttons.get(key_string).getState();
	    	
    		displayInfo(e, "KEY RELEASED: ");
    		monome_buttons.get(key_string).setState(false);
    		if (x!= 9)
    		{
    			if (monome_buttons.get("0").getState() == true)
    			{
    				y = y - 4;
    			}
				pedToApp.sendMsg(x, y, 0);
//				displayDebugInfo(x, y, 0);
    		}
    	}
  	}
    
    /** Handle the button click. */
    public void actionPerformed(ActionEvent e) {
        if ("clear".equals(e.getActionCommand())) {
			if (setupMode == true) {
				listModel.clear();
				origListModel.clear();
				
				transposeDir = "Off";
				transposeStep = 0;
				transposeRange = 0;
				numTransposes = 0;
				debugger.DEBUG("transposeRange reset to: " + transposeRange);
				
				transposeDirectionSelection.setSelectedIndex(4);
				transposeStepSelection.setSelectedIndex(0);
				transposeRangeSelection.setSelectedIndex(0);
			} else {
				displayArea.setText("");
			}
		} else if ("set up".equals(e.getActionCommand())){
			if (setupMode == false) {
				setupMode = true;
				setUpButton.setText("Select Pedal Mode ");
				displayArea.setText("Select monome buttons to step through." + newline
									+ "Use Clear button to empty list." + newline);
									
				// Unhighlight current button selection
				if (listModel.getSize() > 0) {
					String element;
					int x, y;
					element = listModel.getElementAt(buttonListIndex).toString();
					x = Integer.valueOf(element.substring(0,1));
					y = Integer.valueOf(element.substring(2,3));
					leds[x][y].setBackground(Color.lightGray);
					if (element.indexOf(";") > -1) {
						// second grid ref present
						x = Integer.valueOf(element.substring(5,6));
						y = Integer.valueOf(element.substring(7,8));
						leds[x][y].setBackground(Color.lightGray);
					}
				}
			} else {
				setupMode = false;
				multiKeyMode = false;
				setUpButton.setText("Select Set Up Mode");
				displayArea.setText("Position mouse cursor in this window." + newline
									+ "Use left mouse key to effect button press." + newline
									+ "Use right mouse key to cycle through buttons." + newline);
				// Initialise list index
				buttonListIndex = 0;
				// record original button list
				recordOrigButtonList();
				// Highlight first button selection
				if (listModel.getSize() > 0) {
					String element;
					int x, y;				
					element = listModel.getElementAt(buttonListIndex).toString();
					x = Integer.valueOf(element.substring(0,1));
					y = Integer.valueOf(element.substring(2,3));
					leds[x][y].setBackground(new Color(92,183,62));
					if (element.indexOf(";") > -1) {
						// second grid ref present
						x = Integer.valueOf(element.substring(5,6));
						y = Integer.valueOf(element.substring(7,8));
						leds[x][y].setBackground(new Color(92,183,62));
					}
				}
			}
		} else if ("connect".equals(e.getActionCommand())) {
			String deviceName = deviceSelection.getSelectedItem().toString();
			
			// Wire-up step b1:  Set ped_to_mon_port.
			debugger.DEBUG("b1. Setting ped_to_mon_port...");
			ped_to_mon_port = activeDevices.getPortForDevice(deviceName);
			displayArea.setText("selected device: " + deviceName + "; port: "
								+ ped_to_mon_port + newline);
								
			// Wire-up step b2:  Create PedToMon.
			debugger.DEBUG("b2. Creating PedToMon...");
			pedToMon = new PedToMon(ped_to_mon_port);
			
			// Wire-up step b3:  Request PedToMon send ped's mon listening port to mon via /sys/port.
			debugger.DEBUG("b3. PedToMon sending it's mon listening port...");
			pedToMon.sendPedomeRxPort(mon_to_ped_port);
			// Wire-up step b3a. Request PedToMon send ped's prefix to mon via /sys/port.
			debugger.DEBUG("b3a. PedToMon sending it's prefix...");
			pedToMon.sendPedomePrefix("/pedome");
			// Wire-up step b4:  Request PedToMon get mon's status via /sys/info and record mon's prefix.
			debugger.DEBUG("b4. PedToMon requesting mon status...");
			pedToMon.sendInfoReq(mon_to_ped_port);
		} else if ("multi-key".equals(e.getActionCommand())) {
			if (setupMode == true) {
				if (multiKeyMode == false) {
					multiKeyMode = true;
					displayArea.setText("Multi-mode selected..." + newline);
					multiKeyButton.setBackground(new Color(202,108,85));
				}
				else {
					multiKeyMode = false;
					displayArea.setText("Multi-mode de-selected..." + newline);
					multiKeyButton.setBackground(multiKeyButtonOffColor);
				}
			}
		} else if ("transpose dir".equals(e.getActionCommand())) {
			if (setupMode == true) {
					transposeDir = transposeDirectionSelection.getSelectedItem().toString();
					displayArea.setText("Transpose direction selected: " + transposeDir + newline);
			}
		} else if ("transpose step".equals(e.getActionCommand())) {
			if (setupMode == true) {
					transposeStep = Integer.parseInt( transposeStepSelection.getSelectedItem().toString() );
					displayArea.setText("Transpose step selected:" + transposeStep + newline);
			}
		} else if ("transpose range".equals(e.getActionCommand())) {
			if (setupMode == true) {
//					transposeRange = Integer.parseInt( transposeRangeSelection.getSelectedItem().toString() ) - 1;
					transposeRange = Integer.parseInt( transposeRangeSelection.getSelectedItem().toString() );
					displayArea.setText("Transpose range selected:" + transposeRange + newline);
			}
		} else {
			if (setupMode == true) {
				String actionCommand = e.getActionCommand();
				int buttonFlagPosn = actionCommand.indexOf("button");
				if (buttonFlagPosn > 0) {
					int x = Integer.valueOf(actionCommand.substring(0,1));
					int y = Integer.valueOf(actionCommand.substring(2,3));
					if (multiKeyMode == false) {
						displayArea.setText("button selected - x: " + x + "; y: " + y + newline + 
											"and multi-mode is de-selected" + newline);
						if (listModel.getSize() < 8) {
							listModel.addElement(x + "," + y);
						} else {
							displayArea.setText("Maximum number of buttons selected." + newline);
						}
					} else {
						// If list model size > 0 and < 8 then append new x and y to last entry
						// provided last entry only has one x, y.
						int listSize = listModel.getSize();
						if (listSize > 0 && listSize < 8) {
							// Append new <x, y> to existing element
							String element;
							element = listModel.getElementAt(listSize - 1).toString();
							if (element.indexOf(";") == -1) {
							    // element only has one x, y pair, so add another
								element = element + "; " + x + "," + y;
								listModel.setElementAt(element, listSize - 1);
								multiKeyMode = false;
								multiKeyButton.setBackground(multiKeyButtonOffColor);
							}
						}
					}
				}
			}
		}		
    }
	
	/** Handle Mouse Events **/
    public void mousePressed(MouseEvent e) {
//       displayMouseInfo(e, "Mouse pressed; # of clicks: "
//                    + e.getClickCount());

        // Find out which button pressed
        if (e.getButton() == MouseEvent.BUTTON1) {
            // Left button pressed - DO NOTHING
			updatePedalPicture(PedalState.OFF_RIGHT);
        } else if (e.getButton() == MouseEvent.BUTTON2) {
            // Middle button pressed - DO NOTHING
			updatePedalPicture(PedalState.OFF_UP);
        } else if (e.getButton() == MouseEvent.BUTTON3) {
            // Right button pressed
			String element;
			int x, y;
			element = listModel.getElementAt(buttonListIndex).toString();
			x = Integer.valueOf(element.substring(0,1));
			y = Integer.valueOf(element.substring(2,3));
//			displayDebugInfo(x, y, 1);
			pedToApp.sendMsg(x, y, 1);
			if (element.indexOf(";") > -1) {
				// second grid ref present
				x = Integer.valueOf(element.substring(5,6));
				y = Integer.valueOf(element.substring(7,8));
//				displayDebugInfo(x, y, 1);
				pedToApp.sendMsg(x, y, 1);
			}
			updatePedalPicture(PedalState.ON);
		}
	}

    public void mouseReleased(MouseEvent e) {
//       displayMouseInfo(e, "Mouse released; # of clicks: "
//                    + e.getClickCount());

        // Find out which button released
        if (e.getButton() == MouseEvent.BUTTON3) {
            // Right button released
			String element;
			int x, y;
			element = listModel.getElementAt(buttonListIndex).toString();
			x = Integer.valueOf(element.substring(0,1));
			y = Integer.valueOf(element.substring(2,3));
			pedToApp.sendMsg(x, y, 0);
//			displayDebugInfo(x, y, 0);			
			if (element.indexOf(";") > -1) {
				// second grid ref present
				x = Integer.valueOf(element.substring(5,6));
				y = Integer.valueOf(element.substring(7,8));
//				displayDebugInfo(x, y, 0);
				pedToApp.sendMsg(x, y, 0);
			}
 			updatePedalPicture(PedalState.OFF);
       } else if (e.getButton() == MouseEvent.BUTTON1) {
			if (listModel.getSize() > 0) {
				// Left button released - move to next button in list
				String element;
				int x, y;
				
				// Unhighlight old button selection
				element = listModel.getElementAt(buttonListIndex).toString();
				x = Integer.valueOf(element.substring(0,1));
				y = Integer.valueOf(element.substring(2,3));
				leds[x][y].setBackground(Color.lightGray);
				if (element.indexOf(";") > -1) {
					// second grid ref present
					x = Integer.valueOf(element.substring(5,6));
					y = Integer.valueOf(element.substring(7,8));
					leds[x][y].setBackground(Color.lightGray);
				}

				if (buttonListIndex < listModel.getSize() - 1) {
					buttonListIndex++;
				} else {
					buttonListIndex = 0;
				}
			
				// Highlight new button selection
				element = listModel.getElementAt(buttonListIndex).toString();
				x = Integer.valueOf(element.substring(0,1));
				y = Integer.valueOf(element.substring(2,3));
				leds[x][y].setBackground(new Color(92,183,62));
				if (element.indexOf(";") > -1) {
					// second grid ref present
					x = Integer.valueOf(element.substring(5,6));
					y = Integer.valueOf(element.substring(7,8));
					leds[x][y].setBackground(new Color(92,183,62));
				}
				updatePedalPicture(PedalState.OFF);
			}
		} else if (e.getButton() == MouseEvent.BUTTON2) {
			if (listModel.getSize() > 0) {
				// Middle button released - transpose button selection
				String element;
				int x, y;
				
				// Unhighlight old button selection
				element = listModel.getElementAt(buttonListIndex).toString();
				x = Integer.valueOf(element.substring(0,1));
				y = Integer.valueOf(element.substring(2,3));
				leds[x][y].setBackground(Color.lightGray);
				if (element.indexOf(";") > -1) {
					// second grid ref present
					x = Integer.valueOf(element.substring(5,6));
					y = Integer.valueOf(element.substring(7,8));
					leds[x][y].setBackground(Color.lightGray);
				}
				
				// Apply offset
				printButtonList();
				applyOffset();
				printButtonList();
				printOrigButtonList();

				// Highlight new button selection
				element = listModel.getElementAt(buttonListIndex).toString();
				x = Integer.valueOf(element.substring(0,1));
				y = Integer.valueOf(element.substring(2,3));
				leds[x][y].setBackground(new Color(92,183,62));
				if (element.indexOf(";") > -1) {
					// second grid ref present
					x = Integer.valueOf(element.substring(5,6)) ;
					y = Integer.valueOf(element.substring(7,8));
					leds[x][y].setBackground(new Color(92,183,62));
				}
				updatePedalPicture(PedalState.OFF);
			}
		}
	}

    public void mouseEntered(MouseEvent e) {
//       displayMouseInfo(e, "Mouse entered");
    }

    public void mouseExited(MouseEvent e) {
//       displayMouseInfo(e, "Mouse exited");
    }

    public void mouseClicked(MouseEvent e) {
//       displayMouseInfo(e, "Mouse clicked (# of clicks: "
//                    + e.getClickCount() + ")");
	}
	
	private void applyOffset() {
		debugger.DEBUG("Applying offset");
		debugger.DEBUG("transposeDir: " + transposeDir);
		debugger.DEBUG("transposeStep: " + transposeStep);
		debugger.DEBUG("transposeRange: " + transposeRange);
		debugger.DEBUG("numTransposes: " + numTransposes);
		
		String element, updatedElement;
		int x1, y1, x2, y2;
		int listSize = listModel.getSize();
		if (transposeRange == 0 || numTransposes < transposeRange) {
			for (int buttonListIndex = 0; buttonListIndex < listSize; buttonListIndex++)
			{
				element = listModel.getElementAt(buttonListIndex).toString();
				x1 = Integer.valueOf(element.substring(0,1));
				y1 = Integer.valueOf(element.substring(2,3));
				
				// Apply offset to primary button
				if ("Up".equals(transposeDir)) {
					y1 = subtractOffset(y1, transposeStep);
				} else if ("Down".equals(transposeDir)) {
					y1 = addOffset(y1, transposeStep);
				} else if ("Left".equals(transposeDir)) {
					x1 = subtractOffset(x1, transposeStep);
				} else if ("Right".equals(transposeDir)) {
					x1 = addOffset(x1, transposeStep);
				}
				updatedElement = x1 + "," + y1;
				
				// Check for secondary button
				if (element.indexOf(";") > -1) {
					// second grid ref present
					x2 = Integer.valueOf(element.substring(5,6));
					y2 = Integer.valueOf(element.substring(7,8));
					
					// Apply offset to secondary button
					if ("Up".equals(transposeDir)) {
						y2 = subtractOffset(y2, transposeStep);
					} else if ("Down".equals(transposeDir)) {
						y2 = addOffset(y2, transposeStep);
					} else if ("Left".equals(transposeDir)) {
						x2 = subtractOffset(x2, transposeStep);
					} else if ("Right".equals(transposeDir)) {
						x2 = addOffset(x2, transposeStep);
					}
					updatedElement = updatedElement + "; " + x2 + "," + y2;
				}
				
				// Update button list
				listModel.setElementAt(updatedElement, buttonListIndex);
			}
			numTransposes++;
		} else {
			// Restore original button list and zero numTransposes
			debugger.DEBUG("transposeRange exceeded - restoring original button list");
			restoreOrigButtonList();
			numTransposes = 0;
		}
	}
	
	private int subtractOffset(int value, int offset) {
		int updatedValue = value - offset;
		if (updatedValue < 0) {
			updatedValue += 8;
		}
		return updatedValue;
	}

	private int addOffset(int value, int offset) {
		int updatedValue = value + offset;
		if (updatedValue > 7) {
			updatedValue -= 8;
		}
		return updatedValue;
	}
	
	private void printButtonList() {
		String element;
		int listSize = listModel.getSize();
		debugger.DEBUG("Button List contents:");
		for (int buttonListIndex = 0; buttonListIndex < listSize; buttonListIndex++)
		{
			element = listModel.getElementAt(buttonListIndex).toString();
			debugger.DEBUG(element);
		}
	}
   
	private void printOrigButtonList() {
		String element;
		int listSize = origListModel.getSize();
		debugger.DEBUG("Orig Button List contents:");
		for (int buttonListIndex = 0; buttonListIndex < listSize; buttonListIndex++)
		{
			element = origListModel.getElementAt(buttonListIndex).toString();
			debugger.DEBUG(element);
		}
	}
	
	private void recordOrigButtonList() {
		String element;
		int listSize = listModel.getSize();
		debugger.DEBUG("Recording orig button list");
		origListModel.clear();
		for (int buttonListIndex = 0; buttonListIndex < listSize; buttonListIndex++)
		{
			element = listModel.getElementAt(buttonListIndex).toString();
			origListModel.addElement(element);
		}
	}

	private void restoreOrigButtonList() {
		String element;
		int listSize = origListModel.getSize();
		debugger.DEBUG("Restoring orig button list");
		listModel.clear();
		for (int buttonListIndex = 0; buttonListIndex < listSize; buttonListIndex++)
		{
			element = origListModel.getElementAt(buttonListIndex).toString();
			listModel.addElement(element);
		}
	}
	
    private void displayInfo(KeyEvent e, String keyStatus){
        
        //You should only rely on the key char if the event
        //is a key typed event.
        int id = e.getID();
        String keyString;
        if (id == KeyEvent.KEY_TYPED) {
            char c = e.getKeyChar();
            keyString = "key character = '" + c + "'";
        } else {
            int keyCode = e.getKeyCode();
            keyString = "key code = " + keyCode
                    + " ("
                    + KeyEvent.getKeyText(keyCode)
                    + ")";
        }
        
        int modifiersEx = e.getModifiersEx();
        String modString = "extended modifiers = " + modifiersEx;
        String tmpString = KeyEvent.getModifiersExText(modifiersEx);
        if (tmpString.length() > 0) {
            modString += " (" + tmpString + ")";
        } else {
            modString += " (no extended modifiers)";
        }
        
        String actionString = "action key? ";
        if (e.isActionKey()) {
            actionString += "YES";
        } else {
            actionString += "NO";
        }
        
        String locationString = "key location: ";
        int location = e.getKeyLocation();
        if (location == KeyEvent.KEY_LOCATION_STANDARD) {
            locationString += "standard";
        } else if (location == KeyEvent.KEY_LOCATION_LEFT) {
            locationString += "left";
        } else if (location == KeyEvent.KEY_LOCATION_RIGHT) {
            locationString += "right";
        } else if (location == KeyEvent.KEY_LOCATION_NUMPAD) {
            locationString += "numpad";
        } else { // (location == KeyEvent.KEY_LOCATION_UNKNOWN)
            locationString += "unknown";
        }
        
        displayArea.append(keyStatus + newline
                + "    " + keyString + newline
                + "    " + modString + newline
                + "    " + actionString + newline
                + "    " + locationString + newline);
        displayArea.setCaretPosition(displayArea.getDocument().getLength());
    }
	
	private void displayMouseInfo(MouseEvent e, String mouseStatus){
        
        // Find out which button pressed
        int button = e.getButton();
        String buttonString;
        if (button == MouseEvent.BUTTON1) {
            buttonString = "Button 1";
        } else if (button == MouseEvent.BUTTON2) {
            buttonString = "Button 2";
        } else if (button == MouseEvent.BUTTON3) {
            buttonString = "Button 3";
        } else {
			buttonString = "None";
		}
        
        displayArea.append(mouseStatus + newline
                + "    " + buttonString + newline);
        displayArea.setCaretPosition(displayArea.getDocument().getLength());
    }

    private void displayDebugInfo(int x, int y, int state){
        
        //You should only rely on the key char if the event
        //is a key typed event.
        
        displayArea.append(newline
                + "    " + "monome button:" + newline
                + "    " + x + ", " + y + ", " + state + "."
                + newline);
        displayArea.setCaretPosition(displayArea.getDocument().getLength());
    }

	public void setCmdListeningPort(int port) {
		// apps seem to send the /sys/port message a couple of times
		// protect against attempting to wire up to same port tiwce
		if (ped_to_app_port == -1) {
			debugger.DEBUG("in setCmdListeningPort");
			ped_to_app_port = port;

			// Wire-up step c2:  Create PedToApp.
			debugger.DEBUG("c2. Creating PedToApp...");
			pedToApp = new PedToApp(ped_to_app_port);
			
			// Wire-up step c3:	 Respond with /sys/port ped_to_app_port.
			debugger.DEBUG("c3. PedToApp sending ped_to_app_port to app...");
			pedToApp.sendPedomeTxPort(ped_to_app_port);
			sys_port_response_sent_to_app = true;
			if (sys_info_response_sent_to_app && sys_prefix_response_sent_to_app) {
				debugger.DEBUG("++ped and app connected!");
				ped_and_app_connected = true;
				if (ped_and_mon_connected) {
					debugger.DEBUG("++ped and mon already connected...attach forwarders...");
					attachForwarders();
				}
			}
			else debugger.DEBUG("--ped and app not yet connected!");
		}
	}
	
	public void sendPedomeStatus(int port) {
		debugger.DEBUG("in sendPedomeStatus");
		pedToApp.sendPedomeInfo(port);
		sys_info_response_sent_to_app = true;
		if (sys_port_response_sent_to_app && sys_prefix_response_sent_to_app) {
			debugger.DEBUG("++ped and app connected!");
			ped_and_app_connected = true;
			if (ped_and_mon_connected) {
				debugger.DEBUG("++ped and mon already connected...attach forwarders...");
				attachForwarders();
			}
		}
		else debugger.DEBUG("--ped and app not yet connected!");
	}
	
	public void setAppPrefix(String prefix) {
		// create common base class at some point
		// Wire-up step x1:	 Set app prefix.
		debugger.DEBUG("x1. updating app prefix...");
		monToPed.setAppPrefix(prefix);
		pedToApp.setAppPrefix(prefix);
		appToPed.setAppPrefix(prefix);
		
		// Wire-up step x2:	 Respond with /sys/prefix prefix.
		debugger.DEBUG("x2. PedToApp sending app prefix back to app...");
		pedToApp.sendAppPrefix(prefix);
		sys_prefix_response_sent_to_app = true;
		if (sys_port_response_sent_to_app && sys_info_response_sent_to_app) {
			debugger.DEBUG("++ped and app connected!");
			ped_and_app_connected = true;
			if (ped_and_mon_connected) {
				debugger.DEBUG("++ped and mon already connected...attach forwarders...");
				attachForwarders();
			}
		}
		else debugger.DEBUG("--ped and app not yet connected!");
	}
	
	public void monToPedPostProcessing(String prefix) {
		debugger.DEBUG("++ped and mon connected!");
		appToPed.setMonPrefix(prefix);
		ped_and_mon_connected = true;
		if (ped_and_app_connected == true) {
			debugger.DEBUG("++ped and app already connected...attach forwarders...");
			attachForwarders();
		}
	}

	public void attachForwarders() {
		// Wire-up step e1:  Attach PedToMon (forwarder) to AppToPed. Update Listeners for latter.
		debugger.DEBUG("e1. Attaching PedToMon (forwarder) to AppToPed. Updating Listeners for latter...");
		appToPed.attachForwarder(pedToMon);
		// Wire-up step e2:  Attach PedToApp (forwarder) to MonToPed. Update Listeners for latter.
		debugger.DEBUG("e2. Attaching PedToApp (forwarder) to MonToPed. Updating Listeners for latter...");
		monToPed.attachForwarder(pedToApp);
	}

	public void processCmd(int x, int y, int state)
	{
		if (state == 1)
		{
			leds[x][y].setBackground(new Color(244,80,0)); //JOEH
//			updatePedalPicture(PedalState.ON);
		}
		else
		{
			// Set color based upon whether current button is selected
			if (listModel.getSize() > 0) {
				String element;
				int x_set1, y_set1;
				int x_set2 = 21; // out of range num
				int y_set2 = 21;
				element = listModel.getElementAt(buttonListIndex).toString();
				x_set1 = Integer.valueOf(element.substring(0,1));
				y_set1 = Integer.valueOf(element.substring(2,3));
				if (element.indexOf(";") > -1) {
					// second grid ref present
					x_set2 = Integer.valueOf(element.substring(5,6));
					y_set2 = Integer.valueOf(element.substring(7,8));
					leds[x][y].setBackground(Color.lightGray);
				}
				
				if ((x == x_set1 && y == y_set1) || (x == x_set2 && y == y_set2)) {
					leds[x][y].setBackground(new Color(92,183,62));
				} else {
					leds[x][y].setBackground(Color.lightGray);
				}
			} else {
				leds[x][y].setBackground(Color.lightGray);
			}
//			updatePedalPicture(PedalState.OFF);
		}
	}
}
